﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Palmmedia.ReportGenerator.Core.Common;
using Palmmedia.ReportGenerator.Core.Logging;
using Palmmedia.ReportGenerator.Core.Parser;
using Palmmedia.ReportGenerator.Core.Parser.Analysis;
using Palmmedia.ReportGenerator.Core.Parser.FileReading;
using Palmmedia.ReportGenerator.Core.Properties;

namespace Palmmedia.ReportGenerator.Core.Reporting
{
    /// <summary>
    /// Converts a coverage report generated by OpenCoverand other tools into a readable report.
    /// </summary>
    internal class ReportGenerator
    {
        /// <summary>
        /// The Logger.
        /// </summary>
        private static readonly ILogger Logger = LoggerFactory.GetLogger(typeof(ReportGenerator));

        /// <summary>
        /// The file reader to use.
        /// </summary>
        private readonly IFileReader fileReader;

        /// <summary>
        /// The parser to use.
        /// </summary>
        private readonly ParserResult parserResult;

        /// <summary>
        /// The renderers.
        /// </summary>
        private readonly IEnumerable<IReportBuilder> renderers;

        /// <summary>
        /// Initializes a new instance of the <see cref="ReportGenerator" /> class.
        /// </summary>
        /// <param name="fileReader">The file reader.</param>
        /// <param name="parserResult">The parser result to use.</param>
        /// <param name="renderers">The renderers.</param>
        internal ReportGenerator(IFileReader fileReader, ParserResult parserResult, IEnumerable<IReportBuilder> renderers)
        {
            this.fileReader = fileReader ?? throw new ArgumentNullException(nameof(fileReader));
            this.parserResult = parserResult ?? throw new ArgumentNullException(nameof(parserResult));
            this.renderers = renderers ?? throw new ArgumentNullException(nameof(renderers));
        }

        /// <summary>
        /// Starts the generation of the report.
        /// </summary>
        /// <param name="addHistoricCoverage">if set to <c>true</c> historic coverage information is added to classes.</param>
        /// <param name="overallHistoricCoverages">All historic coverage elements.</param>
        /// <param name="executionTime">The execution time.</param>
        /// <param name="tag">The custom tag (e.g. build number).</param>
        internal void CreateReport(bool addHistoricCoverage, List<HistoricCoverage> overallHistoricCoverages, DateTime executionTime, string tag)
        {
            int numberOfClasses = this.parserResult.Assemblies.Sum(a => a.Classes.Count());

            Logger.DebugFormat(Resources.AnalyzingClasses, numberOfClasses);

            int counter = 0;

            foreach (var assembly in this.parserResult.Assemblies)
            {
                foreach (var @class in assembly.Classes)
                {
                    counter++;

                    Logger.DebugFormat(
                        Resources.CreatingReport,
                        counter,
                        numberOfClasses,
                        @class.Assembly.ShortName,
                        @class.Name);

                    var fileAnalyses = @class.Files.Select(f => f.AnalyzeFile(this.fileReader)).ToArray();

                    if (addHistoricCoverage)
                    {
                        var historicCoverage = new HistoricCoverage(@class, executionTime, tag);
                        @class.AddHistoricCoverage(historicCoverage);
                        overallHistoricCoverages.Add(historicCoverage);
                    }

                    Parallel.ForEach(
                        this.renderers,
                        renderer =>
                        {
                            try
                            {
                                renderer.CreateClassReport(@class, fileAnalyses);
                            }
                            catch (Exception ex)
                            {
                                Logger.ErrorFormat(
                                    Resources.ErrorDuringRenderingClassReport,
                                    @class.Name,
                                    renderer.ReportType,
                                    ex.GetExceptionMessageForDisplay());
                            }
                        });
                }
            }

            Logger.Debug(Resources.CreatingSummary);
            SummaryResult summaryResult = new SummaryResult(this.parserResult);

            foreach (var renderer in this.renderers)
            {
                try
                {
                    renderer.CreateSummaryReport(summaryResult);
                }
                catch (Exception ex)
                {
                    Logger.ErrorFormat(
                        Resources.ErrorDuringRenderingSummaryReport,
                        renderer.ReportType,
                        ex.GetExceptionMessageForDisplay());
                }
            }
        }
    }
}